// TODO: CODE REVIEW & CLEANUP!
//
// TouchButton.cs
//
// A button designed for touch-screen use.
//

using System;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;
using HomeUX.ClientControlSupport;
using HomeUX.Synchronization;
using SyncPathClass = HomeUX.Synchronization.SyncPath;

namespace HomeUX.Controls
{

/*
By default, if you set the Text property, text will wrap.  If you'd prefer that the button grow to
accomodate the size of the text, set Width="Auto" and (optionally) set Padding to a desired amount.
For finer control over formatting of the text, set the TextStyle property to a desired style.

Instead of setting Text, you can also set Content to any element.

If you set both Text and Content, the former will appear below the latter.

*/
/// <summary>
/// TODO
/// </summary>
///
[TemplateVisualState(GroupName = CommonStatesName, Name = NormalName)]
[TemplateVisualState(GroupName = CommonStatesName, Name = PressedName)]
[TemplateVisualState(GroupName = CommonStatesName, Name = DisabledName)]
[TemplateVisualState(GroupName = FocusStatesName, Name = UnfocusedName)]
[TemplateVisualState(GroupName = FocusStatesName, Name = FocusedName)]
[TemplateVisualState(GroupName = PseudofocusStatesName, Name = PseudounfocusedName)]
[TemplateVisualState(GroupName = PseudofocusStatesName, Name = PseudofocusedName)]
[TemplateVisualState(GroupName = LitStatesName, Name = UnlitStateName)]
[TemplateVisualState(GroupName = LitStatesName, Name = LitStateName)]
[TemplateVisualState(GroupName = WaitingStatesName, Name = IdleStateName)]
[TemplateVisualState(GroupName = WaitingStatesName, Name = WaitingStateName)]
[DebuggerDisplay("TouchButton {DebugLabel}")]
public class TouchButton : Button, IPseudofocusable, IPerformKeyEvent, IDisposable
{
    const string CommonStatesName = "CommonStates";
    const string NormalName = "Normal";
    const string PressedName = "Pressed";
    const string DisabledName = "Disabled";

    const string FocusStatesName = "FocusStates";
    const string UnfocusedName = "Unfocused";
    const string FocusedName = "Focused";

    const string PseudofocusStatesName = "PseudofocusStates";
    const string PseudounfocusedName = "Pseudounfocused";
    const string PseudofocusedName = "Pseudofocused";
    
    const string LitStatesName = "LitStates";
    const string UnlitStateName = "Unlit";
    const string LitStateName = "Lit";

    const string WaitingStatesName = "WaitingStates";
    const string IdleStateName = "Idle";
    const string WaitingStateName = "Waiting";

    /// <summary>
    /// Implements the connection between this control and the global SyncTree object, which in
    /// turn manages the connection to the server.
    /// </summary>
    SyncHelper _syncHelper;

    bool _isMouseOver;
    bool _isFocused;
    bool _isPressed;

    bool _autoRepeatDelayInProgress;
    DispatcherTimer _autoRepeatTimer;

    public static readonly DependencyProperty IsLitProperty =
        DependencyProperty.Register("IsLit", typeof(bool), typeof(TouchButton),
            new PropertyMetadata(TouchButton.OnIsLitPropertyChanged));
    public static readonly DependencyProperty IsPseudofocusedProperty =
        DependencyProperty.Register("IsPseudofocused", typeof(bool), typeof(TouchButton),
            new PropertyMetadata(TouchButton.OnIsPseudofocusedPropertyChanged));
    public static readonly DependencyProperty IsWaitingProperty =
        DependencyProperty.Register("IsWaiting", typeof(bool), typeof(TouchButton),
            new PropertyMetadata(TouchButton.OnIsWaitingPropertyChanged));
    public static readonly DependencyProperty IsAutoRepeatEnabledProperty =
        DependencyProperty.Register("IsAutoRepeatEnabled", typeof(bool), typeof(TouchButton),
        null);

    public static readonly DependencyProperty LitBackgroundProperty =
        DependencyProperty.Register("LitBackground", typeof(Brush), typeof(TouchButton), null);

    public static readonly DependencyProperty SyncPathProperty =
    DependencyProperty.Register("SyncPath", typeof(string), typeof(TouchButton),
        new PropertyMetadata(TouchButton.OnSyncPathPropertyChanged));

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public static readonly DependencyProperty ItemActionProperty =
        DependencyProperty.Register("ItemAction", typeof(ItemAction), typeof(TouchButton), null);

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public static readonly DependencyProperty SuppressIsWaitingDisplayProperty =
        DependencyProperty.Register("SuppressIsWaitingDisplay", typeof(bool), typeof(TouchButton), null);

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public static readonly DependencyProperty TextProperty =
        DependencyProperty.Register("Text", typeof(string), typeof(TouchButton), null);

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public static readonly DependencyProperty TextStyleProperty =
        DependencyProperty.Register("TextStyle", typeof(Style), typeof(TouchButton), null);

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public event IsWaitingChangedEventHandler IsWaitingChanged;

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public bool IsLit
    {
        get
        {
            return (bool)GetValue(IsLitProperty);
        }
        set
        {
            SetValue(IsLitProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public bool IsPseudofocused
    {
        get
        {
            return (bool)GetValue(IsPseudofocusedProperty);
        }
        set
        {
            SetValue(IsPseudofocusedProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public bool IsWaiting
    {
        get
        {
            return (bool)GetValue(IsWaitingProperty);
        }
        set
        {
            SetValue(IsWaitingProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public bool IsAutoRepeatEnabled
    {
        get
        {
            return (bool)GetValue(IsAutoRepeatEnabledProperty);
        }
        set
        {
            SetValue(IsAutoRepeatEnabledProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public bool SuppressIsWaitingDisplay
    {
        get
        {
            return (bool)GetValue(SuppressIsWaitingDisplayProperty);
        }
        set
        {
            SetValue(SuppressIsWaitingDisplayProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }
        set
        {
            SetValue(TextProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public Style TextStyle
    {
        get
        {
            return (Style)GetValue(TextStyleProperty);
        }
        set
        {
            SetValue(TextStyleProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public Brush LitBackground
    {
        get
        {
            return (Brush)GetValue(LitBackgroundProperty);
        }
        set
        {
            SetValue(LitBackgroundProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public string SyncPath
    {
        get
        {
            return (string)GetValue(SyncPathProperty);
        }
        set
        {
            SetValue(SyncPathProperty, value);
        }
    }

    /// <summary>
    /// TODO
    /// </summary>
    ///
    public ItemAction ItemAction
    {
        get
        {
            return (ItemAction)GetValue(ItemActionProperty);
        }
        set
        {
            SetValue(ItemActionProperty, value);
        }
    }

    // returns a string that attempts to represent the value of <r>Content</r> in as human-readable
    // was as possible, primarily for debugging purposes
    /// <summary>
    /// TODO
    /// </summary>
    ///
    string DebugLabel
    {
        get
        {
			string label;
            object content = Content;
            string stringValue;
            TextBlock textBlock;
            Panel panel;
            if (Text != null)
                label = Text;
            else
            if (content == null)
                label = String.Empty;
            else
            if ((stringValue = content as string) != null)
                label = stringValue;
            else
            if (((textBlock = content as TextBlock) != null) &&
                    !String.IsNullOrEmpty(stringValue = textBlock.Text))
                label = stringValue;
            else
            if ((panel = content as Panel) != null)
            {
                var labelBuilder = new StringBuilder();
                foreach (UIElement child in panel.Children)
                {
                    if (labelBuilder.Length > 0)
                        labelBuilder.Append(' ');
                    if (((textBlock = child as TextBlock) != null) &&
                            !String.IsNullOrEmpty(stringValue = textBlock.Text))
                        labelBuilder.Append(stringValue);
                }
                if (labelBuilder.Length > 0)
                    label = labelBuilder.ToString();
                else
                    label = content.ToString();
            }
            else
                label = content.ToString();
			return label.Replace((char)8232, ' ').Replace('\n', ' ');
        }
    }

    public TouchButton()
    {
        base.DefaultStyleKey = typeof(TouchButton);

        _syncHelper = new SyncHelper(this, 0);

        GotFocus += TouchButton_GotFocus;
        LostFocus += TouchButton_LostFocus;
        MouseEnter += TouchButton_MouseEnter;
        MouseLeave += TouchButton_MouseLeave;

        // hook up <_syncHelper> events
        _syncHelper.IsOnlineChanged += _syncHelper_IsOnlineChanged;
        _syncHelper.IsWaitingChanged += _syncHelper_IsWaitingChanged;
        _syncHelper.PrimaryValueChanged += _syncHelper_PrimaryValueChanged;
    }

    protected override void OnIsPressedChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnIsPressedChanged(e);
        _isPressed = (bool)e.NewValue;
        if (_isPressed)
        {
            if (IsAutoRepeatEnabled)
            {
                if (_autoRepeatTimer == null)
                {
                    _autoRepeatTimer = new DispatcherTimer();
                    _autoRepeatTimer.Tick += delegate
                    {
                        OnClick();
                        if (_autoRepeatDelayInProgress)
                        {
                            _autoRepeatDelayInProgress = false;
                            _autoRepeatTimer.Stop();
                            _autoRepeatTimer.Interval = ClientGlobals.AutoRepeatInterval;
                            _autoRepeatTimer.Start();
                        }
                    };
                }
                _autoRepeatTimer.Interval = ClientGlobals.AutoRepeatDelay;
                _autoRepeatDelayInProgress = true;
                _autoRepeatTimer.Start();
            }
        }
        else
        {
            if (_autoRepeatTimer != null)
                _autoRepeatTimer.Stop();

            // if we're in ControlHelper.IsTouchMode, the following line will remove the focus
            // visual that the base class will try to impose
            UpdateVisualState(false);
        }
    }

    public bool SuppressOrphanCheck { get; set; }

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        //// check if this control is an orphan (Parent == null) -- now, and when Parent could
        //// change
        //CheckOrphanStatus();
        //LayoutUpdated += (sender, e) => CheckOrphanStatus();

        // check if this control is an orphan when Parent may change
        //_syncHelper.CheckOrphanStatus(Parent);
        LayoutUpdated += delegate
        {
            if (!SuppressOrphanCheck)
                _syncHelper.CheckOrphanStatus(Parent);
        };

        // initialize the visual state of the control
        UpdateVisualState(false);
    }

    /// <summary>
    /// Called when <r>_syncHelper</r>.<s>SyncHelper.PrimaryNode</s>.<s>SyncNodeBase.Value</s> has changed -- for example, if another client has
    /// changed the value.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_PrimaryValueChanged(object sender, ValueChangedEventArgs e)
    {
        // NOTE: the following code needs to be executed regardless of whether this control was
        // the originator of this ValueChanged event...

        // select (i.e. light up) or deselect the button if appropriate, based on <ItemAction> and
		// the item data (and the new value of the SyncNode) -- first, set <lit> to the new value
        // of <Lit>
        object newValue = _syncHelper.PrimaryNode.Value;
        bool lit = false;
        object value = Item.GetTypedValue(ItemAction, this);
        //object value = Item.DataToValue(ItemAction, ItemData);
        if (value == null)
        {
            if (newValue != null)
            {
                string stringValue;
                if (((stringValue = newValue as string) != null) && (stringValue.Length > 0))
                    lit = true;
                else
                if (newValue is double)
                    lit = ((double) newValue) != 0;
                else
                if (newValue is bool)
                    lit = (bool) newValue;
            }
        }
        else
        if (newValue != null)
            lit = newValue.Equals(value);

        // apply <lit>
        IsLit = lit;
    }

    /// <summary>
    /// Called when <r>_syncHelper</r><s>SyncHelper.IsWaiting</s> has changed.  This occurs when
    /// <r>_syncHelper</r>.<s>SyncHelper.PrimaryNode</s>.<s>SyncNodeBase.Value</s> is changed on this client, but the change has not yet been
    /// reflected on the server.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_IsWaitingChanged(object sender, EventArgs e)
    {
        // show or hide the wait icon (e.g. hourglass)
        IsWaiting = _syncHelper.IsWaiting;
    }

    /// <summary>
    /// Called when <r>_syncHelper</r><s>SyncHelper.IsOnline</s> has changed.  This occurs when the connection to
    /// the server has been broken or reestablished.  This also occurs when
    /// <r>_syncHelper</r>.<s>SyncHelper.PrimaryNode</s>.<s>SyncNodeBase.Value</s> becomes <n>null</n> (indicating, for example, that the device
    /// associated with that value is offline) or becomes non-<n>null</n>.
    /// </summary>
    ///
    /// <param name="sender">The sender of the event.</param>
    ///
    /// <param name="e">Event arguments.</param>
    ///
    void _syncHelper_IsOnlineChanged(object sender, EventArgs e)
    {
        // enable or disable the control
        IsEnabled = _syncHelper.IsOnline;
    }

    void TouchButton_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
    {
        _isMouseOver = true;
        UpdateVisualState(true);
    }

    void TouchButton_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
    {
        _isMouseOver = false;
        UpdateVisualState(true);
    }

    void TouchButton_GotFocus(object sender, RoutedEventArgs e)
    {
        _isFocused = true;
        UpdateVisualState(true);
    }

    void TouchButton_LostFocus(object sender, RoutedEventArgs e)
    {
        _isFocused = false;
        UpdateVisualState(true);
    }

    static void OnIsLitPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchButton)d;
        target.UpdateVisualState(true);
    }

    static void OnIsPseudofocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchButton)d;
        target.UpdateVisualState(true);
    }

    static void OnIsWaitingPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchButton)d;
        target.UpdateVisualState(true);

        // fire the IsWaitingChanged event
        if (target.IsWaitingChanged != null)
        {
            target.IsWaitingChanged(target, new IsWaitingChangedEventArgs()
            {
                IsWaiting = (bool)e.NewValue
            });
        }
    }

    static void OnSyncPathPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var target = (TouchButton)d;
        target._syncHelper.SyncPath = SyncPathClass.FromString((string)e.NewValue);
    }

    protected override void OnClick()
    {
        // perform the action specified by <ItemAction> and <ItemData>
        if (ItemAction == ItemAction.Navigate)
        {
            // action is to navigate to another screen
            if (ClientGlobals.TryGoToScreen != null)
                ClientGlobals.TryGoToScreen(Tag as string);
        }
        else
        {
            // action is related to the SyncTree
            _syncHelper.PerformItemAction(ItemAction, Item.GetUntypedValue(this), this);
        }

        // base.OnClick will fire the Click event -- since that event might perform actions
        // (e.g. closing a dialog) that would make the _SyncNode.Value changes above not work,
        // we do base.OnClick *after* the code above
        base.OnClick();
    }

    void UpdateVisualState(bool useTransitions)
    {
        /* TODO -- InvalidateLayout?
        if ((_isFocused || _isMouseOver) && !ControlHelper.IsTouchMode)
            VisualStateManager.GoToState(this, Focused2Name, useTransitions);
        else
            VisualStateManager.GoToState(this, Unfocused2Name, useTransitions);
        */

        // if we're in ControlHelper.IsTouchMode, the following code will undo the visual
        // changes made by the base class due to mouse-over and keyboard focus
		if (ClientGlobals.IsTouchMode)
		{
			if (_isFocused)
				VisualStateManager.GoToState(this, UnfocusedName, useTransitions);
			if (_isMouseOver && !_isPressed)
				VisualStateManager.GoToState(this, NormalName, useTransitions);
		}

        if (IsPseudofocused && !ClientGlobals.IsTouchMode)
            VisualStateManager.GoToState(this, PseudofocusedName, useTransitions);
        else
            VisualStateManager.GoToState(this, PseudounfocusedName, useTransitions);

        if (IsLit)
            VisualStateManager.GoToState(this, LitStateName, useTransitions);
        else
            VisualStateManager.GoToState(this, UnlitStateName, useTransitions);

        if (IsWaiting && !SuppressIsWaitingDisplay)
            VisualStateManager.GoToState(this, WaitingStateName, useTransitions);
        else
            VisualStateManager.GoToState(this, IdleStateName, useTransitions);
    }

    public void PerformKeyDown(KeyEventArgs e)
    {
        OnKeyDown(e);
    }

    public void PerformKeyUp(KeyEventArgs e)
    {
        OnKeyUp(e);
    }

    public void Dispose()
    {
        SyncPath = null;
    }
}

}

